package vendedor;

import jade.core.Agent;
import jade.core.behaviours.Behaviour;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.FailureException;
import jade.domain.FIPAAgentManagement.NotUnderstoodException;
import jade.domain.FIPAAgentManagement.Property;
import jade.domain.FIPAAgentManagement.RefuseException;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.proto.SSIteratedContractNetResponder;
import jade.proto.SSResponderDispatcher;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.JOptionPane;

import util.DF;
import util.Mercado;

public class Agente extends Agent  {
	private FrmParam frmParam = null;
	private FrmGUI frmGUI = null;
	final static public String serviceType = "vendedor";
	protected String nome;
	protected String nomeProduto;
	protected float precoMax;
	protected float precoMin;
	protected int prazoMin;
	protected int qualidade;
	protected float probEnganar;
	protected int probEnganarInteiro;

	final float ajustePrecoMaxCBom = 0.95f;
	final float ajustePrecoMaxCSuficiente = 0.9f;
	final float ajustePrecoMaxCMau = 0.8f;
	final float ajustePrecoMaxVenda = 1.3f;

	@Override
	protected void setup() {
		nome = getLocalName();

		// Obter os parâmetros da interface gráfica
		frmParam = new FrmParam(this);
		frmParam.setVisible(true);
	}

	@Override
	protected void takeDown() {
		DF.deregister(this);
		if(frmParam != null) {
			frmParam.dispose();
			frmParam = null;
		}
		if(frmGUI != null) {
			frmGUI.dispose();
			frmGUI = null;
		}
	}

	protected void start(String productName, float precoMax, float precoMin, int prazoMin, int qualidade, int probEnganar) {
		boolean success;
		frmParam = null;
		this.nomeProduto = productName;
		this.precoMax = precoMax;
		this.precoMin = precoMin;
		this.prazoMin = prazoMin;
		this.qualidade = qualidade;
		this.probEnganarInteiro = probEnganar;
		this.probEnganar = ((float)probEnganar / 100.0f);

		// Mostrar a interface gráfica
		frmGUI = new FrmGUI(this);
		frmGUI.setVisible(true);
		
		// Registar no DF
		success = registerServiceInDF();
		if(!success) return;
		
		vender();
	}

	protected void stop() {
		frmParam = null;
		frmGUI = null;
		doDelete();
	}

	private boolean registerServiceInDF() {
		try {
			DF.register(this, nome, serviceType, new Property("nome-produto", nomeProduto));
			frmGUI.addLog("Registado como vendedor de " + nomeProduto);
			return true;
		} catch (FIPAException ex) {
			JOptionPane.showMessageDialog(null, "Erro ao registar este serviço vendedor no DF:\n" + ex.getMessage(), nome, JOptionPane.ERROR_MESSAGE);
			doDelete();
			return false;
		}
	}

	private void vender() {
		addBehaviour(new SSResponderDispatcher(this, MessageTemplate.MatchPerformative(ACLMessage.CFP)) {

			protected Behaviour createResponder(ACLMessage initiationMsg) {
				
				return new SSIteratedContractNetResponder(myAgent, initiationMsg) {

					TreeMap<String, Float> precosVenda = new TreeMap<String, Float>();
					TreeMap<String, Integer> prazosVenda = new TreeMap<String, Integer>();

					protected ACLMessage handleCfp(ACLMessage cfp) throws RefuseException, FailureException, NotUnderstoodException {
						frmGUI.addLog("CFP recebido de " + cfp.getSender().getName() + ". Atributos: " + cfp.getContent());

						if(cfp.getOntology().equals("initial")) { // Iteração inicial
							// Obter os atributos e confirmar o nome do produto
							TreeMap<String, String> atributos = Mercado.messageToProperties(cfp.getContent());
							if(!atributos.containsKey("nome-produto") || !atributos.containsKey("preco-max") || !atributos.containsKey("prazo-max") || !atributos.containsKey("qualidade-min")) {
								frmGUI.addLog("Refused: mensagem inválida");
								throw new RefuseException("mensagem-invalida");
							}
							if(!atributos.get("nome-produto").equals(nomeProduto)) {
								frmGUI.addLog("Refused: nome de produto incorrecto");
								throw new RefuseException("produto-incorrecto");
							}

							// Probabilidade de "enganar" o comprador na qualidade e prazo de venda
							int qld = qualidade;
							int prz = prazoMin;
							boolean aEnganar = false;
							if(Math.random() < probEnganar) {
								qld = 5;
								prz = 1;
								aEnganar = true;
							}
							
							// Verificar os limites dos atributos
							float precoC;
							int prazoC;
							int qualidadeC;
							try {
								precoC = Float.parseFloat(atributos.get("preco-max"));
								prazoC = Integer.parseInt(atributos.get("prazo-max"));
								qualidadeC = Integer.parseInt(atributos.get("qualidade-min"));

								if(precoC < precoMin || prazoC < prz || qualidadeC > qld) {
									frmGUI.addLog("Refused: não interessado");
									throw new RefuseException("nao-interessado");
								}
							}
							catch(NumberFormatException ex) {
								frmGUI.addLog("Refused: mensagem inválida");
								throw new RefuseException("mensagem-invalida");
							}

							// Enviar uma proposta (mínima)
							float preco = Math.min(precoMax, precoC);
							int prazo = Math.max(prazoC, prazoMin);
							if(aEnganar)
								prazo = prz;
							
							atributos.clear();
							atributos.put("preco", "" + preco);
							atributos.put("prazo", "" + prazo);
							atributos.put("qualidade", "" + qld);
							String content = Mercado.propertiesToMessage(atributos);
							frmGUI.addLog("Proposing " + content);
							ACLMessage propose = cfp.createReply();
							propose.setPerformative(ACLMessage.PROPOSE);
							propose.setContent(content);
							precosVenda.put(cfp.getSender().getLocalName(), preco);
							prazosVenda.put(cfp.getSender().getLocalName(), prazo);
							return propose;
						}
						else if(cfp.getOntology().equals("iteration")) { // Iterações seguintes
							// Obter os atributos e classificações
							TreeMap<String, String> atributos = Mercado.messageToProperties(cfp.getContent());
							if(!atributos.containsKey("preco") || !atributos.containsKey("prazo") || !atributos.containsKey("qualidade") || !atributos.containsKey("cpreco") || !atributos.containsKey("cprazo") || !atributos.containsKey("cqualidade")) {
								frmGUI.addLog("Refused: mensagem inválida");
								throw new RefuseException("mensagem-invalida");
							}

							float preco = Float.parseFloat(atributos.get("preco"));
							int prazo = Integer.parseInt(atributos.get("prazo"));
							int qualidade = Integer.parseInt(atributos.get("qualidade"));
							String cpreco = atributos.get("cpreco");
							String cprazo = atributos.get("cprazo");
							String cqualidade = atributos.get("cqualidade");

							// Analisar as classificações e fazer nova proposta
							if(preco == precoMin) {
								frmGUI.addLog("Refused: não interessado");
								throw new RefuseException("nao-interessado");
							}

							if(cpreco.equals("bom")) {
								precoMax = Math.max(precoMax * ajustePrecoMaxCBom, precoMin);
								preco = Math.max(preco * ajustePrecoMaxCBom, precoMin);
							}
							else if(cpreco.equals("suficiente")) {
								precoMax = Math.max(precoMax * ajustePrecoMaxCSuficiente, precoMin);
								preco = Math.max(preco * ajustePrecoMaxCSuficiente, precoMin);
							}
							else {
								precoMax = Math.max(precoMax * ajustePrecoMaxCMau, precoMin);
								preco = Math.max(preco * ajustePrecoMaxCMau, precoMin);
							}

							if(prazo > 1) {
								if(cprazo.equals("suficiente"))
									prazo = Math.max(prazo - 1, prazoMin);
								else if(cprazo.equals("mau"))
									prazo = Math.max(prazo - 2, prazoMin);
							}

							// Enviar a proposta
							atributos.clear();
							atributos.put("preco", "" + preco);
							atributos.put("prazo", "" + prazo);
							atributos.put("qualidade", "" + qualidade);
							String content = Mercado.propertiesToMessage(atributos);
							frmGUI.addLog("Proposing " + content);
							ACLMessage propose = cfp.createReply();
							propose.setPerformative(ACLMessage.PROPOSE);
							propose.setContent(content);
							precosVenda.put(cfp.getSender().getLocalName(), preco);
							prazosVenda.put(cfp.getSender().getLocalName(), prazo);
							return propose;
						}
						else {
							throw new RefuseException("unknown-ontology");
						}
					}
					
					protected ACLMessage handleAcceptProposal(ACLMessage cfp, ACLMessage propose, ACLMessage accept) throws FailureException {
						
						try {
							frmGUI.addLog("Proposta aceite. A entregar o produto...");
							precoMax *= Math.max(ajustePrecoMaxVenda, precoMin);
							if (!precosVenda.containsKey(accept.getSender().getLocalName()) || !prazosVenda.containsKey(accept.getSender().getLocalName())) {
								frmGUI.addLog("Entrega falhada");
								throw new FailureException("error");
							}
							float preco = precosVenda.remove(accept.getSender().getLocalName());
							int prazo = prazosVenda.remove(accept.getSender().getLocalName());
							Thread.sleep(Math.max(prazo, prazoMin) * 1000); // Simular tempo de entrega

							ACLMessage msg = accept.createReply();
							msg.setPerformative(ACLMessage.INFORM);
							TreeMap<String, String> atributos = new TreeMap<String, String>();
							atributos.put("preco", "" + preco);
							atributos.put("qualidade", "" + qualidade);
							msg.setContent(Mercado.propertiesToMessage(atributos));
							frmGUI.addLog("Produto entregue");
							frmGUI.addStat(preco);
							return msg;
						} catch (InterruptedException ex) {
							frmGUI.addLog("Entrega falhada (erro no sleep)");
							throw new FailureException("error");
						}
					}
					
					protected void handleRejectProposal(ACLMessage cfp, ACLMessage propose, ACLMessage reject) {
						frmGUI.addLog("Proposal rejected");
						precoMax = precoMin;
					}
				};
			}
		} );
	}

	private boolean performAction() {
		// Simulate action execution by generating a random number
		return true;
	}
}
